package smoothing;

import java.io.IOException;
import java.util.List;
import java.util.Map;

import parser.Parser;

/**
 * Estimates probabilities according to training using Lidstone smoothing
 */
public class Lidstone implements Smoothing {
		
	private Map<String, Integer> obsVoc; // observed vocabulary in data set
	private long numEventsTrain; // number of events in train set
	private double lambda; //smoothing parameter
	
	/**
	 * Constructor- creates a new object implementing Lidstone method 
	 * @param trainFileName name of train set file
	 * @param obsVoc observed vocabulary from data set
	 * @param lambda smoothing parameter
	 * @throws IOException if had errors reading files
	 */
	public Lidstone(String trainFileName, Map<String, Integer> obsVoc,
			double lambda) throws IOException {
		
		// count number of events in train set
		numEventsTrain = Parser.numEvents(trainFileName);
		
		// set members
		this.obsVoc = obsVoc;
		this.lambda = lambda;
	}
	
	/**
	 * Sets the observed vocabulary
	 * @param newObsVoc new observed vocabulary
	 */
	public void setObsVoc(Map<String, Integer> newObsVoc) {
		this.obsVoc = newObsVoc;
	}
	
	/**
	 * Sets the smoothing parameter
	 * @param newLambda new smoothing parameter
	 */
	public void setLambda(double newLambda) {
		this.lambda = newLambda;
	}
	
	/**
	 * Returns number of different words (events) in train file
	 * @return size of observed vocabulary
	 */
	public int numDifferentEvents() {
		
		// return size of observed vocabulary				
		return obsVoc.size();
	}

	/**
	 * Returns number of instances of given word in the
	 * observed vocabulary
	 * @param inputWord word to check
	 * @return number of instances of given word in the observed vocabulary
	 */
	public int numInstances(String inputWord) {				
		return obsVoc.get(inputWord);
	}
	
	/**
	 * Calculates the MLE based on the training set (no smoothing)
	 * for given word
	 * @param inputWord word to check
	 * @return word frequency divided by total number of events in training set
	 */
	public double mleTrain(String inputWord) {		
		return ((double) obsVoc.get(inputWord)) / numEventsTrain;
	}
	
	/**
	 * Returns the probability hat MLE assigns to a word not in the training set
	 * @return returns 0
	 */
	public double mleTrain() {
		return 0.0;
	}

	/**
	 * Calculates the MLE for given word, using Lidstone smoothing
	 */
	public double mleModel(String inputWord) {
		
		// calculate numerator
		double numerator = lambda;
		if (obsVoc.containsKey(inputWord)) { // check if observed word
			numerator = obsVoc.get(inputWord) + lambda;
		}			
		
		// calculate denominator
		double denominator = numEventsTrain + Parser.VOCAB_SIZE*lambda;
		
		// return MLE
		return numerator / denominator;
	}
	
	/**
	 * Calculates the MLE for a word not in the training set, using
	 * Lidstone smoothing
	 * @param lambda smoothing parameter
	 * @return MLE for an unseen word according to Lidstone smoothing
	 */
	public double mleModel() {
		
		// calculate numerator
		double numerator = lambda;
		
		// calculate denominator
		double denominator = (double)numEventsTrain + ((double)Parser.VOCAB_SIZE)*lambda;
		
		// return MLE
		return numerator / denominator;
	}
	
	/**
	 * Returns true if the probabilities' sum is 1, otherwise false
	 * @return correctness of the smoothing
	 */
	@Override
	public boolean debug() {
		
		// number of unseen events
		long unseenEventsNum = Parser.VOCAB_SIZE - obsVoc.size();
		
		// sum the probabilities of the unseen events
		double unseenProbabilitiesSum = mleModel() * unseenEventsNum;
		
		// sum the probabilities of the observed events
		double obsEventsProbabilitiesSum = 0;
		
		for (Map.Entry<String, Integer> currEntry : obsVoc.entrySet()) {
		
			obsEventsProbabilitiesSum += mleModel(currEntry.getKey());
			}
		
		// sum of the probabilities in the method 
		double probabilitiesSum = unseenProbabilitiesSum + obsEventsProbabilitiesSum;
		
		// in case the total sum is 1 --> return true
		if (probabilitiesSum == 1)
			return true;
		
		// otherwise --> false
		return false;
	}

	/**
	 * Return the expected frequency for given frequency, using Lidstone smoothing
	 */
	@Override
	public double expectedFrequency(int frequency) {

		// calculate numerator
		double numerator = frequency + lambda;		
		
		// calculate denominator
		double denominator = numEventsTrain + Parser.VOCAB_SIZE * lambda;
		
		// the estimated probability
		double estimatedProbability = numerator / denominator;
		
		// return the expected frequency
		return estimatedProbability * numEventsTrain;
	}

}









